package com.ysu.zyw.common.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.ysu.zyw.common.exception.SystemException;
import org.apache.shiro.codec.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

/**
 * EncodeUtil provide a set of operation to encrypt.
 * <p>
 * Created by ZeRur on 2016/1/17.
 *
 * @author yaowu.zhang
 */
public class EncryptUtil {

    private static final Logger logger = LoggerFactory.getLogger(EncryptUtil.class);

    /**
     * md5 encrypt.
     *
     * @param plainText plain text
     * @return encrypt result
     */
    public static String md5(String plainText) {
        Assert.hasText(plainText, "empty plain text is not allowed");
        return irreversibleEncrypt(plainText, IrreversibleEncryptAlgorithm.MD5);
    }

    /**
     * sha1 encrypt.
     *
     * @param plainText plain text
     * @return encrypt result
     */
    public static String sha1(String plainText) {
        Assert.hasText(plainText, "empty plain text is not allowed");
        return irreversibleEncrypt(plainText, IrreversibleEncryptAlgorithm.SHA1);
    }

    /**
     * aes encrypt.
     *
     * @param plainText plain text
     * @param password     password
     * @return encrypt result
     */
    public static String aesEncrypt(String plainText, String password) {
        Assert.hasText(plainText, "empty plain text is not allowed");
        Assert.hasText(password, "empty password is not allowed");
        return reversibleEncrypt(plainText, password, ReversibleEncryptAlgorithm.AES);
    }

    /**
     * aes decrypt.
     *
     * @param cipherText cipher str
     * @param password  password
     * @return decrypt result
     */
    public static String aesDecrypt(String cipherText, String password) {
        Assert.hasText(cipherText, "empty cipher text is not allowed");
        Assert.hasText(password, "empty password is not allowed");
        return reversibleDecrypt(cipherText, password, ReversibleEncryptAlgorithm.AES);
    }

    private enum IrreversibleEncryptAlgorithm {
        MD5, SHA1
    }

    public enum ReversibleEncryptAlgorithm {
        AES
    }

    /**
     * irreversible encrypt
     */
    private static String irreversibleEncrypt(String plainText, IrreversibleEncryptAlgorithm algorithm) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance(algorithm.toString());
        } catch (NoSuchAlgorithmException e) {
            logger.error("EncryptUtils - encrypt - ", e);
            throw new SystemException(e);
        }
        messageDigest.update(plainText.getBytes());

        StringBuilder strCipherText = new StringBuilder();
        byte[] byteCipherText = messageDigest.digest();
        for (byte aByteCipherText : byteCipherText) {
            int temp = aByteCipherText;
            if (temp < 0) {
                temp += 256;
            }
            if (temp < 16) {
                strCipherText.append("0");
            }
            strCipherText.append(Integer.toHexString(temp));
        }
        return strCipherText.toString();
    }

    /**
     * reversible encrypt
     */
    private static String reversibleEncrypt(String strPlainText, String password,
                                            ReversibleEncryptAlgorithm algorithm) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm.toString());
            keyGenerator.init(128, new SecureRandom(password.getBytes()));
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, algorithm.toString());
            Cipher cipher = Cipher.getInstance(algorithm.toString());
            byte[] byteContent = strPlainText.getBytes("UTF-8");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// 初始化
            byte[] result = cipher.doFinal(byteContent);
            return Base64.encodeToString(result);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
                | BadPaddingException | UnsupportedEncodingException e) {
            logger.error("EncryptUtils - reversibleEncrypt - ", e);
            throw new SystemException(e);
        }
    }

    /**
     * reversible decrypt
     */
    private static String reversibleDecrypt(String content, String password, ReversibleEncryptAlgorithm algorithm) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm.toString());
            keyGenerator.init(128, new SecureRandom(password.getBytes()));
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, algorithm.toString());
            Cipher cipher = Cipher.getInstance(algorithm.toString());
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            byte[] result = cipher.doFinal(Base64.decode(content));
            return new String(result);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
                | BadPaddingException e) {
            logger.error("EncryptUtils - reversibleDecrypt - ", e);
            throw new SystemException(e);
        }
    }
}
